﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: ObjectQuery_1.cs
//
//  Description: A storage independent query.
// 
//--------------------------------------------------------------------------

using System;
using System.Reflection;
using System.Collections.Generic;
using Chili.Opf3.Storages;
using Chili.Opf3.Query.OPathParser;

namespace Chili.Opf3.Query
{
	/// <summary>
	/// Represents a storage independent query.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class represents a storage independent query. The query is then compiled by the
	/// framework and executed on a class that implements the <see cref="Opf3.Storages.IStorage">IStorage</see>
	/// interface. When creating an ObjectQuery you may specify different conditions, simple joins and 
	/// sort options.
	/// </para>
	/// <para>
	/// ObjectQueries may be used in any part of your application, since they are storage independent.
	/// You should always check if you can use an ObjectQuery before switching to the 
	/// <see cref="Opf3.Query.SqlQuery">SqlQuery</see>. Perhaps you can move some of the joins directly 
	/// to the storage (stored procedure or view) to avoid using <see cref="Opf3.Query.SqlQuery">SqlQuery</see>.
	/// </para>
	/// <para><br /><br />
	/// <b>OPath</b><br />
	/// OPath is a storage indepentend query language that is transformed to a storage dependent query at runtime.
	/// The language understands various different types of statements right now. In a statement you must always 
	/// use the property names of the corresponding persistent object. All the OPath is translated to a 
	/// storage depentend query when "compiled" (at runtime).<br />
	/// The following statements are supported:
	/// <br /><br />
	/// <b>Condition</b><br />
	/// A condition is a property name, a comparer and a parameter. You can use inline parameter or tags ({0}, {1}, ...)
	/// that are replaced by the parameter at runtime. Using tags is strongly recommended (for dynamic parameters)
	/// to avoid injections.
	/// <br/>
	/// Samples: "UserName = {0}", "Duration &lt; {0}", "UserName = 'Test'", "Duration &lt; 10", "UserName Like {0}"
	/// <br /><br />
	/// <b>Simple Joins</b><br />
	/// Simple joins allow you to join an related object and check some properties of that object. If the properties
	/// fullfil the conditions the original object is loaded (obsolete!).
	/// <br />
	/// Samples: "Call[Duration &lt; 10]" checks if one of the connected calls of the user is shorter then 10 
	/// minutes.
	/// <br /><br />
	/// <b>Sort Options</b><br />
	/// Sort Options allow to sort the result of the query. They consist in a keyword "SortBy", the name of the
	/// property that is sorted by and the sort direction. Currently only one property is allowed in a sort option.
	/// <br />
	/// Samples: "SortBy UserName Asc", "SortBy UserName Desc"
	/// <br /><br />
	/// <b>Concats and brackets</b><br />
	/// You can concat each condition to another and brackets are also allowed in a OPath statement.
	/// <br />
	/// Samples: "UseName = {0} And Password = {1}", "(Duration &lt; 10 And Name = {1}) Or Name = 'Test'"
	/// <br /><br />
	/// <b>Custom commands</b><br />
	/// OPath supports also custom commands. The commands must be understood by the storage to work. The storage
	/// itself converts them to a command that is then executed on the storage. The storage gets a 
	/// <see cref="Opf3.Query.OPathParser.OECommand">OECommand</see> object that contains the name
	/// of the command and could also contain arguments. See SubString below, which contains 1 and 3 as arguments.
	/// <br />
	/// Samples: "Upper(UserName)" is converted by Oracle to "TO_UPPER({0})" the {0} Tag stands for the name of the
	/// field. "SubString(UserName, 1, 3)" is converted to a command that is understood by the storage. 
	/// </para>
	/// </remarks>
	/// <example>
	/// The following example shows how to use ObjectQuery in your application.
	/// <code lang="CS">
	/// // ... Other code
	/// 
	/// // Create an ObjectQuery for User persistent objects.
	/// // The query contains the following:
	/// // - Two conditions that check the user name of the objects and load only the matching ones.
	/// // - A simple join that loads only user with one or more calls of duration 1.
	/// // - A sort option that sorts the result by UserName.
	/// ObjectQuery&lt;User&gt; objectQuery = new ObjectQuery&lt;User&gt;(
	///		"(UserName Like {0} Or UserName Like {1}) And Call[Duration = {2}] SortBy UserName Asc",
	///		"%hri%", "%No%", 1);
	/// 
	/// // Get an ObjectSet that contains the result of the query.
	/// ObjectSet&lt;User&gt; objectSet = context.GetObjectSet&lt;User&gt;(query);
	/// 
	/// // ... Other code.
	/// </code>
	/// </example>
	[Serializable]
	public sealed class ObjectQuery<T> : IQuery
	{
		private QueryTypes _queryType = QueryTypes.Load;
		private string _conditions;
		private int _timeout = 30;
		private IList<object> _parameters;

		/// <summary>
		/// Creates a new instance of the <see cref="ObjectQuery{T}">ObjectQuery Class</see>.
		/// </summary>
		internal ObjectQuery()
		{
		}

		/// <summary>
		/// Creates a new instance of the <see cref="ObjectQuery{T}">ObjectQuery Class</see>.
		/// </summary>
		/// <param name="conditions">A OPath expression that is trasformed to a storage depentend query when
		/// executed on the storage.</param>
		/// <param name="parameters">Parameters of the OPath expression. All tags in the OPath expression
		/// are replaced by the parameters that are set here.</param>
		public ObjectQuery(string conditions, params object[] parameters)
		{
			_conditions = conditions;
			if (parameters != null)
				_parameters = new List<object>(parameters);
		}

		/// <summary>
		/// Specifies the OPath expression of the <see cref="ObjectQuery{T}">ObjectQuery</see>.
		/// </summary>
		/// <value>Represents the OPath expression set in the constructor.</value>
		internal string Value
		{
			get { return _conditions; }
		}

		/// <summary>
		/// Returns the list of parameters associated with the OPath expression.
		/// </summary>
		internal IList<object> Parameters
		{
			get { return _parameters; }
		}

		#region IQuery Members

		/// <summary>
		/// Converts the <see cref="IQuery">IQuery</see> to an
		/// <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see> that is then executed on a
		/// specific <see cref="Opf3.Storages.IStorage">storage</see>.
		/// </summary>
		/// <returns>
		/// A new <see cref="CompiledQuery">CompiledQuery</see> that has been generated using this instance.
		/// </returns>
		/// <param name="storage">
		/// Class that implements the <see cref="Opf3.Storages.IStorage">IStorage</see>
		/// interface. The query is executed on that storage.
		/// </param>
		public CompiledQuery ToCompiledQuery(IStorage storage)
		{
			if (storage == null)
				throw new ArgumentNullException("storage");

			// Return the compiled CompiledQuery.
			return OPath.Parse<T>(this).ToCompiledQuery(storage);
		}

		/// <summary>
		/// Specifies the wait time before terminating the attempt to execute a command and generating an error.
		/// </summary>
		/// <value>An integer that specifies the wait time in seconds.</value>
		/// <exception cref="ArgumentException">Value is lower then zero.</exception>
		public int Timeout
		{
			get { return _timeout; }
			set 
			{
				if (value < 0)
					throw new ArgumentException("Value is lower then zero.");
				_timeout = value; 
			}
		}

		/// <summary>
		/// Returns the <see cref="Opf3.Query.QueryTypes">QueryType</see> of the 
		/// <see cref="ObjectQuery{T}">ObjectQuery</see>.
		/// </summary>
		/// <value>A member of the <see cref="Opf3.Query.QueryTypes">QueryType</see> enumeration.</value>
		public QueryTypes QueryType
		{
			get { return _queryType; }
		}

		#endregion
	}
}
